
package com.dlc.shop.allinpay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.dlc.shop.common.allinpay.bean.*;
import com.dlc.shop.common.allinpay.util.OpTrustManager;
import com.dlc.shop.common.allinpay.util.OpenUtils;
import com.dlc.shop.common.allinpay.util.SecretUtils;
import com.dlc.shop.common.config.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author lanhai
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class OpenClient {
    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    private static final String CHARSET = "utf-8";
    private final OpenConfig config;
    private final PrivateKey privateKey;
    private final PublicKey tlPublicKey;
    private static final Logger logger = LoggerFactory.getLogger(OpenClient.class);
    private static final String TRACE_ID_HEADER = "x-traceId-header";

    public OpenClient(OpenConfig config) {
        config.validate();
        this.config = config;
        this.privateKey = SecretUtils.loadPrivateKey(null, config.getCertPath(), config.getCertPwd());
        this.tlPublicKey = SecretUtils.loadTlPublicKey(config.getTlCertPath());
    }

    public OpenResponse execute(String method, BizParameter param) {
        OpenRequest request = this.assembleRequest(method, param);
        logger.info("request:{}", request);
        String paramStr = this.encodeOnce((Map<String, Object>) JSON.toJSON(request));
        logger.info("post body:{}", paramStr);
        Map<String, String> respMap = this.post(paramStr);
        String body = respMap.get("body");
        logger.info("response:{}", body);
        OpenResponse resp = this.verify(body);
        resp.setTraceId(respMap.get("traceId"));
        return resp;
    }

    public void asynNotifyForServer(String notifyType, BizParameter param, String notifyUrl, String clientAppId) {
        AsynNotiyRequest request = this.assembleAsynRequest(notifyType, param, notifyUrl, clientAppId);
        this.post(this.encodeOnce((Map<String, Object>) JSON.toJSON(request)));
    }

    public String concatUrlParams(String method, BizParameter param) {
        OpenRequest request = this.assembleRequest(method, param);
        return this.config.getUrl() + "?" + this.encodeOnce((Map<String, Object>) JSON.toJSON(request));
    }

    public String concatUrlForServer(String method, BizParameter param, String jumpUrl, String clientAppId) {
        OpenRequest request = this.assembleServiceRequest(method, param, jumpUrl, clientAppId);
        return this.config.getUrl() + "?" + this.encodeOnce((Map<String, Object>) JSON.toJSON(request));
    }

    public String encrypt(String content) {
        return SecretUtils.encryptAes(content, this.config.getSecretKey());
    }

    public String decrypt(String content) {
        return SecretUtils.decryptAes(content, this.config.getSecretKey());
    }

    public boolean checkSign(String signedValue, String sign) {
        return SecretUtils.verify(this.tlPublicKey, signedValue, sign, this.config.getSignType());
    }

    private OpenResponse verify(String resp) throws OpException {
        JSONObject map = JSON.parseObject(resp);
        String sign = map.getString("sign");
        map.remove("sign");
        String signedValue = JSON.toJSONString(map, SerializerFeature.MapSortField);
        if (!SecretUtils.verify(this.tlPublicKey, signedValue, sign, this.config.getSignType())) {
            throw new OpException("verify sign error");
        } else {
            return JSON.parseObject(resp, OpenResponse.class);
        }
    }

    private OpenRequest assembleServiceRequest(String method, BizParameter param, String jumpUrl, String clientAppId) {
        OpenUtils.assertNotNull(param, "param must not be null ");
        OpenServiceRequest request = new OpenServiceRequest();
        request.setAppId(this.config.getAppId());
        request.setClientAppId(clientAppId);
        request.setJumpUrl(jumpUrl);
        request.setMethod(method);
        request.setFormat(this.config.getFormat());
        request.setCharset(this.config.getCharset());
        request.setVersion(this.config.getVersion());
        request.setTimestamp((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()));
        request.setBizContent(param.toString());
        request.setNotifyUrl(this.config.getNotifyUrl());
        String signedValue = this.getSignedValue((Map<String, Object>) JSON.toJSON(request));
        logger.info("待签名源串:{}", signedValue);
        String sign = SecretUtils.sign(this.privateKey, signedValue, this.config.getSignType());
        request.setSignType(this.config.getSignType());
        request.setSign(sign);
        return request;
    }

    private AsynNotiyRequest assembleAsynRequest(String noticeType, BizParameter param, String notifyUrl, String clientAppId) {
        OpenUtils.assertNotNull(param, "param must not be null");
        AsynNotiyRequest request = new AsynNotiyRequest();
        request.setAppId(this.config.getAppId());
        request.setClientAppId(clientAppId);
        request.setNotifyUrl(notifyUrl);
        request.setNotifyType(noticeType);
        request.setCharset(this.config.getCharset());
        request.setNotifyTime((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()));
        request.setVersion(this.config.getVersion());
        request.setBizContent(param.toString());
        request.setNotifyUrl(this.config.getNotifyUrl());
        String signedValue = this.getSignedValue((Map<String, Object>) JSON.toJSON(request));
        logger.debug("待签名源串：{}", signedValue);
        String sign = SecretUtils.sign(this.privateKey, signedValue, this.config.getSignType());
        request.setSign(sign);
        request.setSignType(this.config.getSignType());
        request.setSign(sign);
        return request;
    }

    private OpenRequest assembleRequest(String method, BizParameter param) {
        OpenUtils.assertNotNull(param, "param must not be null");
        OpenRequest request = new OpenRequest();
        request.setAppId(this.config.getAppId());
        request.setMethod(method);
        request.setFormat(this.config.getFormat());
        request.setCharset(this.config.getCharset());
        request.setTimestamp((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()));
        request.setVersion(this.config.getVersion());
        request.setBizContent(param.toString());
        String signedValue = this.getSignedValue((Map<String, Object>) JSON.toJSON(request));
        logger.debug("待签名源串:{}", signedValue);
        String sign = SecretUtils.sign(this.privateKey, signedValue, this.config.getSignType());
        request.setSign(sign);
        request.setSignType(this.config.getSignType());
        return request;
    }

    private String getSignedValue(Map<String, Object> reqMap) {
        Map<String, String> copy = new TreeMap<>();

        for (Map.Entry<String, Object> entry : reqMap.entrySet()) {
            if (entry.getValue() != null) {
                copy.put(entry.getKey(), entry.getValue().toString());
            }
        }

        StringBuilder sb = new StringBuilder(Constant.INITIAL_CAPACITY);

        for (Map.Entry<String, String> entry : copy.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }

        return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1);
    }

    private Map<String, String> post(String param) {
        Map<String, String> resultMap = new HashMap<>(Constant.INITIAL_CAPACITY);
        StringBuilder result = new StringBuilder(Constant.INITIAL_CAPACITY);
        BufferedWriter writer = null;
        BufferedReader reader = null;
        String traceId = "";

        try {
            URL httpUrl = new URL(this.config.getUrl());
            HttpURLConnection connection = (HttpURLConnection) httpUrl.openConnection();
            if (connection instanceof HttpsURLConnection) {
                ((HttpsURLConnection) connection).setSSLSocketFactory(OpTrustManager.instance().getSslSocketFactory());
            }

            connection.setRequestProperty("Connection", "keep-alive");
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8));
            writer.write(param);
            writer.flush();
            Map<String, List<String>> headerFields = connection.getHeaderFields();
            Iterator<Map.Entry<String, List<String>>> var10 = headerFields.entrySet().iterator();

            label148:
            while (true) {
                Map.Entry entry;
                do {
                    if (!var10.hasNext()) {
                        reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));

                        String line;
                        while ((line = reader.readLine()) != null) {
                            result.append(line);
                        }
                        break label148;
                    }

                    entry = var10.next();
                } while (!"x-traceId-header".equalsIgnoreCase((String) entry.getKey()));

                traceId = entry.getValue() != null && !((Collection) entry.getValue()).isEmpty() ? (String) ((List) entry.getValue()).get(0) : "";
            }
        } catch (Exception var20) {
            logger.error(var20.getMessage(), var20);
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }

                if (reader != null) {
                    reader.close();
                }
            } catch (IOException var19) {
                logger.error(var19.getMessage(), var19);
            }

        }

        resultMap.put("body", result.toString());
        resultMap.put("traceId", traceId);
        return resultMap;
    }

    private String encodeOnce(Map<String, Object> jo) {
        StringBuilder sb = new StringBuilder(Constant.INITIAL_CAPACITY);

        try {
            for (Map.Entry<String, Object> entry : jo.entrySet()) {
                String value = entry.getValue() == null ? "" : (String) entry.getValue();
                sb.append(entry.getKey()).append("=").append(URLEncoder.encode(value, "utf-8")).append("&");
            }
        } catch (UnsupportedEncodingException var6) {
            logger.error("encodeOnce Error", var6);
        }

        return sb.substring(0, sb.length() - 1);
    }

    public PrivateKey getPrivateKey() {
        return this.privateKey;
    }
}
